跳到主要内容

gRPC gateway 数据的转换

通常把 gRPC 用作内部通信,而使用 Restful Api 进行外部通信。为了避免写两套应用(或者为了兼容旧服务),我们使用 grpc-gateway 把 gRPC 转成 HTTP。服务接收到 HTTP 请求后,grpc-gateway 把它转成 gRPC 进行处理,然后以 JSON 形式返回数据。

这里使用 gRPC-Gateway 这个开源库,gRPC-Gateway 是一个插件,它为 gRPC 服务生成反向代理服务器,将 Restful/JSON 转换为 gRPC,反之亦然。

换句话说,gRPC-Gateway 将在你的 gRPC 服务上创建一个层,该层将充当客户端的 Restful/JSON 服务。gRPC-Gateway 从 gRPC 服务的 Protocol Buffer 的定义生成代码。

20220530110824

然后使用 go get 获取 grpc-gateway。

首先安装依赖

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u github.com/golang/protobuf/protoc-gen-go;

工作的流程

1、首先定义 .proto 文件; 2、然后由 protoc 将 .proto 文件编译成 protobuf 格式的数据; 3、将 2 中编译后的数据传递到各个插件,生成对应语言、对应模块的源代码。

Go Plugins 用于生成 .pb.go 文件 gRPC-Gateway 则是 pb.gw.go

项目结构

└── src
└── grpc-helloworld-gateway
├── gateway
│   └── main.go
├── greeter_server
│   └── main.go
└── helloworld
├── helloworld.pb.go
├── helloworld.pb.gw.go
└── helloworld.proto

具体使用方式

安装 protoc 文件

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.14.5

下载之后就能在 github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis\google\api 目录下找到对应的 proto 文件了

20220601114330

执行完上述命令后,就会将 protoc-gen-grpc-gateway 下载到电脑的 GOPATH 下,自己电脑的 GOPATH 可以通过命令 go env 查看。

所以对应的就是

$GOPATH\pkg\mod\github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis

所以引用路径就是

# 注意: 路径中要用 '/'
protoc -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis \
--go_out=plugins=grpc:. ./*.proto

生成对应服务

创建 helloworld.proto 文件,里面引入 google 官方的 api 相关的扩展,为 grpc 的 http 转换提供了支持。

syntax = "proto3";

package helloworld;

import "google/api/annotations.proto";

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/example/echo"
body: "*"
};
}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

这里的 google/api/annotations.proto 是谷歌官方

这里增加了对 http 的扩展配置。

option (google.api.http) = {
post: "/v1/example/echo"
body: "*"

然后编译 proto 文件,生成对应的 go 文件

cd src/grpc-helloworld-gateway

# -I 指定 protoc 的搜索 import 的 proto 的文件夹。
protoc -I/usr/local/include -I. \
-I$GOPATH/pkg/mod/ \
-I$GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis \
--go_out=plugins=grpc:. \
helloworld/helloworld.proto

这里生成了 helloworld/helloworld.pb.go 文件。

20220601161038

helloworld.pb.go 是 server 服务需要的,下一步我们需要使用 protoc 生成 gateway 需要的 go 文件。

# 生成 Swagger 文件与 gateway 文件
protoc -I/usr/local/include -I. \
-I$GOPATH/pkg/mod/ \
-I$GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
--swagger_out=logtostderr=true:. \
helloworld/helloworld.proto

20220601162123

编写 Gateway 服务

package main

import (
"flag"
"google.golang.org/grpc/credentials/insecure"
"net/http"

"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"

gw "alsritter.icu/grpc-gateway/helloworld"
)

var (
echoEndpoint = flag.String("echo_endpoint", "localhost:50051", "endpoint of YourService")
)

func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()

mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
if err != nil {
return err
}

return http.ListenAndServe(":8080", mux)
}

func main() {
flag.Parse()
defer glog.Flush()

if err := run(); err != nil {
glog.Fatal(err)
}
}

首先 echoEndpoint 存储了需要连接的 server 信息,然后将这些信息和新建的 server 用 gw.go 中的 RegisterGreeterHandlerFromEndpoint 进行一个注册和绑定

这时底层就会连接 echoEndpoint 提供的远程 server 地址,这样 gateway 就作为客户端和远程 server 建立了连接,之后用 http 启动新建的 server,gateway 就作为服务器端对外提供 http 的服务了。

启动服务后就可以使用 http 去请求了(别忘了关代理)

$ go run greeter_server/main.go

$ curl -X POST -k http://localhost:8080/v1/example/echo -d '{"name": " world"}'
{"message":"Hello world"}

curl 用 post 向 gateway 发送请求,gateway 作为 proxy 将请求转化一下通过 grpc 转发给 greeter_server,然后 greeter_server 通过 grpc 返回结果,gateway 收到结果后,转化成 json 返回给前端。

生成 swagger 文档

由上面的命令,已经生成了 helloworld.swagger.json 可以将其导入任何支持 OpenAPI 规范的平台进行浏览,例如:

References

gRPC-Gateway 官方文档